package com.packenius.library.xspdf;

import java.awt.Color;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Description of a single PDF page.
 * @author Christian Packenius, 2013.
 */
class XSPage {
  /**
   * The document this page belongs to.
   */
  final XSPDF xsPDF;

  /**
   * Number of this page (first page has number 1).
   */
  final int pageID;

  /**
   * Page fontSize of this PDF page.
   */
  XSPageSize pageSize;

  /**
   * All used fonts in this page.
   */
  final Set<XSFontTypeAndEncodingInformation> usedFontTypesAndEncodings = new HashSet<XSFontTypeAndEncodingInformation>();

  /**
   * All columns within this page.
   */
  final List<XSColumn> columns = new ArrayList<XSColumn>();

  /**
   * Current column to write in.
   */
  XSColumn currentColumn = null;

  /**
   * Margin of this page.
   */
  XSPageMargin pageMargin;

  /**
   * Background color for the whole page.
   */
  Color backgroundColor;

  /**
   * Transition mode between the previous page and this one. Only useful for presentation mode in fullscreen mode.
   */
  XSTransitionMode transitionMode;

  /**
   * How long to show the current page before the next page is shown (in fullscreen mode only?).
   */
  double presentationDuration = 0.0;

  /**
   * Page label of this page (page number to show in PDF Viewer).
   */
  XSPageLabel pageLabel;

  /**
   * Object ID in the PDF document stream.
   */
  int pdfObjectID = -1;

  /**
   * Constructor.
   * @param xsPDF Document this page belongs to.
   * @param pageID Page number.
   * @param pageSize Initial page fontSize of this page.
   * @param backgroundColor Background color of this page.
   * @param pageLabel PageLabel to use for this page.
   */
  XSPage(XSPDF xsPDF, int pageID, XSPageSize pageSize, XSPageMargin pageMargin, Color backgroundColor,
      XSTransitionMode transitionMode, double presentationDurationPerPage, XSPageLabel pageLabel) {
    this.xsPDF = xsPDF;
    this.pageID = pageID;
    this.pageSize = pageSize;
    this.pageMargin = pageMargin;
    this.backgroundColor = backgroundColor;
    this.transitionMode = transitionMode;
    presentationDuration = presentationDurationPerPage;
    this.pageLabel = pageLabel;
  }

  boolean addTextPart(String part, XSFontUsage fontUsage, XSLink link) {
    forceColumn();

    // Add text part to column that can contain the text.
    while (currentColumn != null && !currentColumn.addTextPart(part, fontUsage, link)) {
      setNextColumn();
    }

    return currentColumn != null;
  }

  void setFirstColumn() {
    if (columns.isEmpty()) {
      if (xsPDF.standardColumns.isEmpty()) {
        createStandardColumn();
      } else {
        for (XSColumn column : xsPDF.standardColumns) {
          addColumn(column.x, column.y, column.width, column.height, column.name, column.margin,
            column.backgroundColor, column.layerID);
        }
      }
    }
    currentColumn = columns.get(0);
    informListenerForNewColumn();
  }

  private void informListenerForNewColumn() {
    for (XSContentListener listener : xsPDF.contentListeners) {
      listener.newColumn(xsPDF, pageID, currentColumn.name);
    }
  }

  private void createStandardColumn() {
    addColumns(1, 1);
  }

  void setNextColumn() {
    if (currentColumn == null) {
      throw new XSPdfException("Coding error @setNextCurrentColumn() #1!");
    }
    int k = columns.indexOf(currentColumn);
    if (k < 0) {
      throw new XSPdfException("Coding error @setNextCurrentColumn() #2!");
    }
    k++;
    if (k >= columns.size()) {
      currentColumn = null;
    } else {
      currentColumn = columns.get(k);
      informListenerForNewColumn();
    }
  }

  void setPageSize(XSPageSize pageSize) {
    if (hasAnyContent()) {
      throw new XSPdfException("Can't change page size after adding content to page!");
    }
    this.pageSize = pageSize;
    double dx = pageSize.width / 10.0;
    double dy = pageSize.height / 10.0;
    pageMargin = new XSPageMargin(dy, dy, dx, dx, dx / 2.0, dy / 2.0);
  }

  boolean hasAnyContent(int layerID) {
    for (XSColumn column : columns) {
      if (column.layerID == layerID && column.hasAnyContent()) {
        return true;
      }
    }
    return false;
  }

  boolean hasAnyContent() {
    for (XSColumn column : columns) {
      if (column.hasAnyContent()) {
        return true;
      }
    }
    return false;
  }

  void setImage(String imageID, double x, double y, double width, double height, int pixelWidth, int pixelHeight,
      double margin, XSLink link) {
    forceColumn();
    currentColumn.setImage(imageID, x, y, width, height, pixelWidth, pixelHeight, margin, link);
  }

  private void forceColumn() {
    if (currentColumn == null) {
      setFirstColumn();
    }
  }

  void setPageEnd() {
    if (currentColumn != null) {
      currentColumn.setColumnEnd();
    }
  }

  private void addColumns(int columnCount, int columnRowCount) {
    double xMarginSum = pageMargin.left + pageMargin.right + (columnCount - 1) * pageMargin.columnGapX;
    double yMarginSum = pageMargin.top + pageMargin.bottom + (columnRowCount - 1) * pageMargin.columnGapY;
    double columnWidth = (pageSize.width - xMarginSum) / columnCount;
    double columnHeight = (pageSize.height - yMarginSum) / columnRowCount;
    if (columnWidth <= 0 || columnHeight <= 0) {
      throw new XSPdfException("Page too small or pageMargin/gaps too large or too many columns on page!");
    }
    double margin = Math.min(Math.min(pageMargin.left, pageMargin.right), Math.min(pageMargin.top, pageMargin.bottom));
    double dy = pageMargin.top;
    for (int y = 0; y < columnRowCount; y++) {
      double dx = pageMargin.left;
      for (int x = 0; x < columnCount; x++) {
        addColumn(dx, dy, columnWidth, columnHeight, null, margin, xsPDF.currentColumnBackgroundColor,
          xsPDF.currentLayerID);
        dx += pageMargin.columnGapX + columnWidth;
      }
      dy += pageMargin.columnGapY + columnHeight;
    }
  }

  void addColumn(double dx, double dy, double columnWidth, double columnHeight, String name, double margin,
      Color backgroundColor, int layerID) {
    if (hasAnyContent(layerID)) {
      throw new XSPdfException("Can't add column after adding content to layer " + layerID + "!");
    }
    columns.add(new XSColumn(this, dx, dy, columnWidth, columnHeight, name, margin, backgroundColor, layerID));
  }

  void setPageMargin(XSPageMargin pageMargin) {
    if (hasAnyContent()) {
      throw new XSPdfException("Can't change page margin after adding content to page!");
    }
    this.pageMargin = pageMargin;
  }

  /**
   * Sets the background color of this page.
   * @param color New background color.
   */
  void setBackgroundColor(Color color) {
    backgroundColor = color;
  }
}
